#!/usr/bin/python
# -*- coding: utf-8 -*-
from typing import List

import pandas as pd

from core.dataClasses import StockTradeDataColumnName
from core.enums import Period
from infrastructure.util import dateutils
from core.tactic import Tactic
from index import momentum
from core.inventedtrader import TradeType

"""
动量交易策略（Moment Trading Strategy）。
在经典力学里，动量（Momentum）指的是物体质量和速度的乘积。牛视顿当初在创造动量这个概念时，意图捕捉的是“运动的量”这个概念。所以我们可以理解为物体必须有运动才会有动量。
背后的直觉：当物体有运动，就会有速度；速度加上物体本身的质量，就决定了动量。速度和质量一方面描述了物体的运动状态，另一方面也刻画出其保持已有运动状态的趋势，即惯性大小。
类比于证券市场上，如果我们把“证券价格“类比成“运动中的物体“，证券价格的“上涨或下跌“则可以为物体运动。
依据惯性的原理，证券价格上涨，则其有继续上涨的动能，也可以说证券价格保持着上涨的动量；同理，证券价格下跌，则其可能有继续下跌的动量。
动量效应产生的原因：与传统金融理论相悖，行为金融学则认为投资人在复杂的市场环境中无法完美地预测与判断，故有“非理性”的行为与随之而来的定价偏差；同时，套利者受限于市场机制与风险承担能力，也不一定能及时纠正偏差的价格。
另一种说法“正反馈模式“：借由羊群效应来说明动量产生的原因。大多数投资人有从众心理，认知或判断倾向于公众舆论或行为，证券市场即有“赢者恒赢，输者横输”的现象。
第三种说法“过度反应“：是指投资人对私有信息的预测性，自身的投资判断能力等高估而产生的过度反应。更甚者，短期的趋势变化“不出所料“这个心理、行为现象会被进一步强化。

动量交易策略的一般思路：
1. 获取股票价格（一般为收盘价）数据；
2. 确定时间跨度和动量表达式，计算股票的动量值；
3. 根据动量指标制定交易策略：
    在动量指标运用上，最直觉的交易策略是动量大于0，说明股票可能还具备上涨的动量，释放出买入的信号；
    当股票的动量值小于0，说明股票可能有下跌的动量，释放出卖出的信号。
    简而言之，若动量大于0，则买入股票；若动量小于0，则卖出股票。
4. 交易策略的回测与评价。
"""


class SubtractionMomentumTactic(Tactic):
    """
    做差法：即今天的价格减去一段时间间隔（m期）以前的价格，用公式表示t时期的m期动量Momentum_t:
        Momentum_t = P_t - P_{t-m}
    P_t:为股票t时期的价格
    m:表示时间间隔
    P_{t-m}:为股票在t-m期的价格
    """

    def __init__(self, trade_types: List[TradeType] = [TradeType.BUY, TradeType.SELL], period = Period.day):
        super(SubtractionMomentumTactic, self).__init__()
        self.trade_types = trade_types
        self.period = period

    def setup(self, context):
        df = context.data  # type:pd.DataFrame
        column = momentum.SUBTRACTION_MOMENTUM_COLUMN_PREFIX + str(self.period)
        if column not in df.columns.values:
            momentum.SubtractionMomentum(self.period, key=StockTradeDataColumnName.CLOSE).compute(context.data)
        if self.trade_types is None or len(self.trade_types)==0 or TradeType.BUY in self.trade_types:
            self.buy_date = df[df[column] > 0 > df[column].shift(1)].index.strftime(
                dateutils.DateFormat.Y_m_d).values
        if self.trade_types is None or len(self.trade_types)==0 or TradeType.SELL in self.trade_types:
            self.sell_date = df[df[column] < 0 < df[column].shift(1)].index.strftime(
                dateutils.DateFormat.Y_m_d).values


class ROCMomentumTactic(Tactic):
    """
    做除法求出的动量值衡量的是价格变化的比率。
    ROC(Rate Of Change)：
        ROC_t = (P_t - P_{t-m})/P_{t-m}
        ROC_tb表示股票t时期的m期动量值（价格变化率），P_t表示股票t时期的价格，m表示时间间隔，P_{t-m}表示股票在t-m期的价格。
    对于时间间隔m的单位，有天、周、月或者年等。在投资时间和金融研究中，较为常见的时间跨度有1个月、3个月、6个月、9个月或者12个月。
    以10天、20天、25天时间间隔的动量一般被看做短期动量，以6个月、9个月为时间跨度的动量一般被看做中期动量，而长期动量的时间间隔一般在1年以上甚至更久。
    """

    def __init__(self, trade_types: List[TradeType] = [TradeType.BUY, TradeType.SELL], period = Period.day):
        super(SubtractionMomentumTactic, self).__init__()
        self.trade_types = trade_types
        self.period = period

    def setup(self, context):
        df: pd.DataFrame = context.data
        column = momentum.ROC_MOMENTUM_COLUMN_PREFIX + str(self.period)
        if column not in df.columns.values:
            momentum.ROC(self.period, key=StockTradeDataColumnName.CLOSE).compute(context.data)
        if self.trade_types is None or len(self.trade_types) == 0 or TradeType.BUY in self.trade_types:
            self.buy_date = df[df[column] > 0 > df[column].shift(1)].index.strftime(
                dateutils.DateFormat.Y_m_d).values
        if self.trade_types is None or len(self.trade_types)==0 or TradeType.SELL in self.trade_types:
            self.sell_date = df[df[column] < 0 < df[column].shift(1)].index.strftime(
                dateutils.DateFormat.Y_m_d).values
